using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
using ValidationFramework.Reflection;
using PropertyDescriptor=ValidationFramework.Reflection.PropertyDescriptor;

namespace ValidationFramework.Web.ASP
{
    /// <summary>
    /// Adds <see cref="BaseValidator"/>s to <see cref="Control"/>s based on the <see cref="Rule"/>s on a <see cref="Type"/>.
    /// </summary>
    /// <remarks>
    /// The following <see cref="Rule"/>s are supported for front end javascript validation.
    /// <list type="bullet">
    ///  <item>
    ///		<see cref="CompareRule{T}"/>
    ///	 </item>
    ///  <item>
    ///		<see cref="CompareRule{T}"/>. But only for the following types <see cref="short"/>, <see cref="int"/>, <see cref="long"/>, <see cref="decimal"/>, <see cref="double"/>, <see cref="DateTime"/><br/>
    ///   Note: see <B>Note on <see cref="DateTime"/>s</B> 
    ///	 </item>
    ///  <item>
    ///		<see cref="LengthStringRule"/>
    ///	 </item>
    ///  <item>
    ///		<see cref="RangeStringRule"/>
    ///	 </item>
    ///  <item>
    ///		<see cref="RangeRule{T}"/>. But only for the following types <see cref="short"/>, <see cref="int"/>, <see cref="long"/>, <see cref="decimal"/>, <see cref="double"/>, <see cref="DateTime"/><br/>
    ///   Note: see <B>Note on <see cref="DateTime"/>s</B> <br/>
    ///   Note: is either <see cref="RangeRule{T}.EqualsMaximumIsValid"/> or <see cref="RangeRule{T}.EqualsMinimumIsValid"/> are set to false no <see cref="BaseValidator"/>s will be generated. This is due to extensibility restrictions of the MS web validators and will be fixed in future versions.
    ///	 </item>
    ///  <item>
    ///		<see cref="RegexRule"/>
    ///	 </item>
    ///  <item>
    ///		<see cref="RequiredStringRule"/>
    ///	 </item>
    ///  <item>
    ///		<see cref="RequiredRule{T}"/>. Note: see <B>Note on <see cref="DateTime"/>s</B> 
    ///	 </item>
    /// </list>
    /// All <see cref="Rule"/>s, including those listed above, will be validated server side. <br/>
    /// 
    /// <B>Note on <see cref="DateTime"/>s</B>: <see cref="DateTime"/>s will only work if the <see cref="RequiredRule{T}.InitialValue"/>, <see cref="CompareRule{T}.ValueToCompare"/>, <see cref="RangeRule{T}.Minimum"/> and <see cref="RangeRule{T}.Maximum"/> is a <see cref="DateTime"/> with no time component. This is due to the limitation of both the <see cref="RequiredFieldValidator"/> and <see cref="CompareValidator"/>, since they only support "yyyy-MM-dd". Note that if a <see cref="RequiredRule{T}"/> is applied to a <see cref="DateTime"/> property that is <b>not</b> <see cref="Nullable{T}"/> <see cref="DateTime.MinValue"/> will be used as the <see cref="RequiredRule{T}.InitialValue"/>.
    /// </remarks>
    public class PropertyValidatorGenerator : BaseValidatorGenerator
    {
        #region Fields

        #endregion


        #region Properties

        /// <summary>
        /// Gets or sets the fully qualified name of the target <see cref="Type"/> to validate.
        /// </summary>
        [Description("The fully qualified name of the target Type to validate.")]
        public RuntimeTypeHandle TypeToValidate
        {
            get;
            set;
        }

        #endregion


        #region Events

        /// <summary>
        /// Occurs when the <see cref="ParameterValidatorGenerator"/> requires a target object for validation. 
        /// </summary>
        public EventHandler<TargetObjectRequiredEventArgs> TargetObjectRequired { get; set; }

        #endregion


        #region Methods

        private ValidationError ServerValidateHandler(Rule propertyRule, InfoDescriptor infoDescriptor)
        {
            var controls = new List<Control>();
            foreach (var association in ValidationAssociations)
            {
                controls.Add(association.ControlToValidate);
            }

            var propertyDescriptor = (PropertyDescriptor) infoDescriptor;
            ValidationError error;
            if (propertyDescriptor.IsStatic)
            {
                error = propertyRule.BaseValidate(null, propertyDescriptor.GetValue(null), Context, null);
            }
            else
            {
                var targetObjectRequiredEventArgs = new TargetObjectRequiredEventArgs(controls);
                OnTargetObjectRequired(targetObjectRequiredEventArgs);

                if (targetObjectRequiredEventArgs.TargetObject == null)
                {
                    throw new InvalidOperationException("No target object could be found to validate. You must attach to ValidatorGenerator.TargetObjectRequired to provide a target object for server validates.");
                }
                error = propertyRule.BaseValidate(targetObjectRequiredEventArgs.TargetObject, propertyDescriptor.GetValue(targetObjectRequiredEventArgs.TargetObject), Context, null);
            }
            return error;
        }


        /// <summary>
        /// Raises the <see cref="TargetObjectRequired"/> event. 
        /// </summary>
        /// <param name="objectRequiredEventArgs">A <see cref="TargetObjectRequiredEventArgs"/> that contains the event data.</param>
        /// <returns>The target object to be validated.</returns>
        protected virtual void OnTargetObjectRequired(TargetObjectRequiredEventArgs objectRequiredEventArgs)
        {
            TargetObjectRequired(this, objectRequiredEventArgs);
        }


		/// <inheritdoc />
        protected override InfoDescriptor GetInfoDescriptor(string memberName)
        {
            var targetTypeDescriptor = TypeCache.GetType(TypeToValidate);
            PropertyDescriptor propertyDescriptor;
            targetTypeDescriptor.Properties.TryGetValue(memberName, out propertyDescriptor);
            return propertyDescriptor;
        }

		/// <inheritdoc />
        protected override void CreateServerValidator(ValidationAssociation association, IList<BaseValidator> validators, Rule rule, InfoDescriptor infoDescriptor)
        {
            var serverWebValidator = new ServerWebValidator(rule, infoDescriptor, ServerValidateHandler);
            validators.Add(serverWebValidator);
            AddValidator(association, serverWebValidator);
        }

        #endregion
    }
}